Skip to main content

Been Counter

I've struggled to get PyCrypto's counter mode doing what I want, so I've turned ECB mode into CTR myself. My counter can go both upwards and downwards to throw off cryptanalysts! There's no chance they'll be able to read my picture.

loading
source.py
from Crypto.Cipher import AES

KEY = ?

class StepUpCounter(object):
def __init__(self, value=os.urandom(16), step_up=False):
self.value = value.hex()
self.step = 1
self.stup = step_up

def increment(self):
if self.stup:
self.newIV = hex(int(self.value, 16) + self.step)
else:
self.newIV = hex(int(self.value, 16) - self.stup)
self.value = self.newIV[2:len(self.newIV)]
return bytes.fromhex(self.value.zfill(32))

def __repr__(self):
self.increment()
return self.value

@chal.route('/bean_counter/encrypt/')
def encrypt():
cipher = AES.new(KEY, AES.MODE_ECB)
ctr = StepUpCounter()

out = []
with open("challenge_files/bean_flag.png", 'rb') as f:
block = f.read(16)
while block:
keystream = cipher.encrypt(ctr.increment())
xored = [a^b for a, b in zip(block, keystream)]
out.append(bytes(xored).hex())
block = f.read(16)

return {"encrypted": ''.join(out)}

Solution

This WriteUp Solution is password protected by the flag of the challenge.

If you notice the code carefully you will find that the keystream is generated by encrypting the counter which is not changing due to a bug.So if we get the keystream we can get image by xoring it with the encrypted image.

The thing here is that how to retrieve the keystream. since, each png image starts with same 16 bytes header

89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52

So the code for retrieving the flag image is:

solve.py
import requests

png_header = bytes([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52])
url = "http://aes.cryptohack.org/bean_counter/encrypt/"

r=requests.get(url)
encrypted = bytes.fromhex(r.json()['encrypted'])

keystream = []
for i in range(len(png_header)):
keystream.append(png_header[i] ^ encrypted[i])

png = [0]*len(encrypted) #recover png bytes
for i in range(len(encrypted)):
png[i] = encrypted[i] ^ keystream[i%len(keystream)]

# store them in a image
with open('bean_counter.png', 'wb') as f:
f.write(bytes(png))

so after running the script we get the flag image which is: crypto{hex_bytes_beans}